/*
 * ExporterToKML.java
 *
 * Created on 26 aout 2006, 10:12
 *
 */

package courselog.gpsutils;

import ewe.io.BufferedOutputStream;
import ewe.io.File;
import ewe.io.FileOutputStream;
import ewe.io.IOException;
import ewe.util.Vector;

/**
 * Object to export a track into a KML format.
 * The object is associated to the TrackRecord or Vector of TrackRecords object to write.
 * The exporter also colors the output track according to a flag value which can address
 * altitude, time or speed.
 * @author rigal
 */
public class ExporterToKML {
    
    /** Identifier of altitude data flag (==1)*/
    public final static int DF_ALTITUDE=1;
    
    /** Identifier of time data flag (==2)*/
    public final static int DF_TIME=2;
    
    /** Identifier of speed data flag (==3)*/
    public final static int DF_SPEED=3;
    
    /** Header of XML */
    public final static byte sXSHead[]=
            "<?xml version=\"1.0\" standalone=\"yes\"?>\n".getBytes();
    
    /** Header of KML */
    public final static byte[] sXSKMLHead=
            ("<kml creator=\"CourseLog http://courselog.free.fr/\" xmlns="+
            "\"http://earth.google.com/kml/2.0\">\n").getBytes();
    
    /** End of KML */
    public final static byte[] sXCKMLHead="</kml>".getBytes();
    
    /** Header of Document */
    public final static byte[] sXSDoc="<Document>\n".getBytes();
    
    /** close Document */
    public final static byte[] sXCDoc="</Document>\n".getBytes();
    
    /** Tag for visibility */
    public static final byte[] sXTVisibility="<visibility>1</visibility>\n".getBytes();
    
    /** Tag for open */
    public static final byte[] sXTOpen="<open>1</open>\n".getBytes();
    
    /** Header of Folder */
    public final static byte[] sXSFolder="<Folder>\n".getBytes();
    
    /** close Folder */
    public final static byte[] sXCFolder="</Folder>\n".getBytes();
    
    /** Header of Placemark */
    public final static byte[] sXSPlace="<Placemark>\n".getBytes();
    
    /** close Placemark */
    public final static byte[] sXCPlace="</Placemark>\n".getBytes();
    
    /** Header of Name */
    public final static byte[] sXSName="<name>".getBytes();
    
    /** close Name */
    public final static byte[] sXCName="</name>\n".getBytes();
    
    /** Header of Bold */
    public final static byte[] sXSBold="<b>".getBytes();
    
    /** close Bold */
    public final static byte[] sXCBold="</b>".getBytes();
    
    /** Header of Point */
    public final static byte[] sXSPoint="<![CDATA[<span style=\"color:#".getBytes();
    
    /** Intermediate of Point */
    public final static byte[] sXIPoint="\">".getBytes();
    
    /** close Point */
    public final static byte[] sXCPoint="</span>]]>".getBytes();
    
    /** Header of LineString */
    public final static byte[] sXSLine=("<LineString>\n"+
//            "<altitudeMode>clampedToGround</altitudeMode>\n"+
            "<coordinates>").getBytes();
    
    /** close LineString */
    public final static byte[] sXCLine=("</coordinates>\n"+
            "<tessellate>1</tessellate>\n"+
            "</LineString>\n").getBytes();
    
    /** Header of Style */
    public final static byte[] sXSStyle=("<Style>\n"+
            "<LineStyle>\n"+
            "<color>").getBytes();
    
    /** close Style*/
    public final static byte[] sXCStyle=("</color>\n"+
            "<width>2</width>\n"+
            "</LineStyle>\n"+
            "</Style>\n").getBytes();
    
    /** color Map, values for red*/
    public final static byte[] clMapRed[]={"00".getBytes(),"00".getBytes(),
    "00".getBytes(),"00".getBytes(),"00".getBytes(),"00".getBytes(),
    "20".getBytes(),"60".getBytes(),"A0".getBytes(),"E0".getBytes(),
    "FF".getBytes(),"FF".getBytes(),"FF".getBytes(),"FF".getBytes(),
    "E0".getBytes(),"A0".getBytes()};
    /** color Map, values for Green*/
    public final static byte[] clMapGreen[]={"00".getBytes(),"00".getBytes(),
    "20".getBytes(),"60".getBytes(),"A0".getBytes(),"E0".getBytes(),
    "FF".getBytes(),"FF".getBytes(),"FF".getBytes(),"FF".getBytes(),
    "E0".getBytes(),"A0".getBytes(),"60".getBytes(),"20".getBytes(),
    "00".getBytes(),"00".getBytes()};
    /** color Map, values for Blue */
    public final static byte[] clMapBlue[]={"A0".getBytes(),"E0".getBytes(),
    "FF".getBytes(),"FF".getBytes(),"FF".getBytes(),"FF".getBytes(),
    "E0".getBytes(),"A0".getBytes(),"60".getBytes(),"20".getBytes(),
    "00".getBytes(),"00".getBytes(),"00".getBytes(),"00".getBytes(),
    "00".getBytes(),"00".getBytes()};
    /** color Map, value for transparency */
    public final static byte[] clTransparent="CC".getBytes();
    
    /**
     * Track record to convert to KML.
     */
    protected TrackRecord myTr=null;
    
    /**
     * Vector of records to convert to KML tracks.
     */
    protected Vector myVect=null;
    
    /** Minimum value for mapped value */
    protected double myMinVal=0.0d;
    
    /** Maximum value for mapped value */
    protected double myMaxVal=0.0d;
    
    /**
     * Creates a new instance of ExporterToKML.
     * Initialises with a single track.
     * @param track Track to convert.
     */
    public ExporterToKML(TrackRecord track) {
        myTr=track;
    }
    
    /**
     * Creates a new instance of ExporterToKML.
     * Initialise with a vector of TrackRecord elements.
     * @param vect Tracks to convert.
     */
    public ExporterToKML(Vector vect) {
        for(int idx=vect.size()-1; idx>=0; idx--) {
            if(!(vect.elementAt(idx) instanceof TrackRecord)) return;
        }
        if(vect.size()>0) myVect=vect;
    }
    
    /**
     * Export track(s) to a file.
     * @param fOut File to write to.
     * @param dataFlag Identification of data to associate.
     */
    public void exportToFile(File fOut, int dataFlag) {
        byte[] buf=new byte[20];
        int lg=0;
        byte[][] bLat=new byte[2][20];
        int[] iLat=new int[2];
        byte[][] bLon=new byte[2][20];
        int[] iLon=new int[2];
        byte[][] bVal=new byte[2][20];
        int[] iVal=new int[2];
        
        try {
            FileOutputStream fWFile= new FileOutputStream(fOut);
            BufferedOutputStream fW= new BufferedOutputStream(fWFile);
            
            fW.write(sXSHead);
            fW.write(sXSKMLHead);
            fW.write(sXSDoc);
            fW.write(sXSName);
            fW.write("CourseLog tracks".getBytes());
            fW.write(sXCName);
            
            // First folder is the legend
            
            fW.write(sXSFolder);
            fW.write(sXSName);
            fW.write("Legend :".getBytes());
            switch(dataFlag) {
                case DF_ALTITUDE: fW.write(" Altitude (m)".getBytes()); break;
                case DF_TIME: fW.write(" Time (s)".getBytes()); break;
                case DF_SPEED: fW.write(" Speed (km/h)".getBytes()); break;
            }
            // Update min and max values for legend
            scanMinMaxLegendValues(dataFlag);
            
            fW.write(sXCName);
            for(int idxV=0; idxV<clMapRed.length; idxV++) {
                fW.write(sXSPlace);
                fW.write(sXSName);
                fW.write(sXSPoint);
                // Write color in RGB
                fW.write(clMapRed[idxV]);
                fW.write(clMapGreen[idxV]);
                fW.write(clMapBlue[idxV]);
                fW.write(sXIPoint);
                fW.write(sXSBold);
                
                double vLegend=
                        (myMaxVal-myMinVal)*idxV/(clMapRed.length-1)+myMinVal;
                switch(dataFlag) {
                    case DF_ALTITUDE:
                        lg=formatAltitude(buf,vLegend); break;
                    case DF_TIME:
                        lg=formatDuration(buf,vLegend); break;
                    case DF_SPEED:
                        lg=formatSpeed(buf,vLegend); break;
                }
                fW.write(buf,0,lg);
                fW.write(sXCBold);
                fW.write(sXCPoint);
                fW.write(sXCName);
                fW.write(sXTVisibility);
                fW.write(sXCPlace);
            }
            fW.write(sXTOpen);
            fW.write(sXTVisibility);
            fW.write(sXCFolder);
            
            // Second folder is the tracks
            fW.write(sXSFolder);
            fW.write(sXSName);
            fW.write("Tracks".getBytes());
            fW.write(sXCName);
            
            // iterate on tracks
            int nbTracks=1;
            if(myVect!=null) nbTracks=myVect.size();
            TrackRecord trCur=myTr;
            int idxB=0;
            
            for(int idxTrk=0; idxTrk<nbTracks; idxTrk++) {
                fW.write(sXSFolder);
                fW.write(sXSName);
                fW.write("Track".getBytes());
                lg=formatIdxPoint(buf,idxTrk);
                fW.write(buf,0,lg);
                fW.write(sXCName);
                if(myVect!=null) trCur=(TrackRecord) myVect.elementAt(idxTrk);
                
                // First point
                TrackPoint lastPt= (TrackPoint) trCur.vPoints.elementAt(0);
                iLat[idxB]=formatLatOrLon(bLat[idxB],lastPt.lat);
                iLon[idxB]=formatLatOrLon(bLon[idxB],lastPt.lon);
                switch(dataFlag) {
                    case DF_ALTITUDE:
                        iVal[idxB]=formatAltitude(bVal[idxB],lastPt.altSeaLevel);
                        break;
                    case DF_TIME:
                        iVal[idxB]=formatDuration(bVal[idxB],lastPt.durationHere);
                        break;
                    case DF_SPEED:
                        iVal[idxB]=formatSpeed(bVal[idxB],lastPt.speed);
                        break;
                }
                idxB=1-idxB;
                
                int nbPt=trCur.vPoints.size();
                for(int idxP=1; idxP<nbPt; idxP++) {
                    
                    
                    // Format new point
                    TrackPoint tPt= (TrackPoint) trCur.vPoints.elementAt(idxP);
                    iLat[idxB]=formatLatOrLon(bLat[idxB],tPt.lat);
                    iLon[idxB]=formatLatOrLon(bLon[idxB],tPt.lon);
                    double val=0.0d;
                    switch(dataFlag) {
                        case DF_ALTITUDE:
                            val=tPt.altSeaLevel;
                            iVal[idxB]=formatAltitude(bVal[idxB],val);
                            break;
                        case DF_TIME:
                            val=tPt.durationHere;
                            iVal[idxB]=formatDuration(bVal[idxB],val);
                            break;
                        case DF_SPEED:
                            val=tPt.speed;
                            iVal[idxB]=formatSpeed(bVal[idxB],val);
                            break;
                    }
                    
                    // Write XML
                    fW.write(sXSPlace);
                    fW.write(sXSName);
                    fW.write(sXSPoint);
                    // Write color in RGB
                    int idxC=colorIndexOf(val);
                    fW.write(clMapRed[idxC]);
                    fW.write(clMapGreen[idxC]);
                    fW.write(clMapBlue[idxC]);
                    fW.write(sXIPoint);
                    fW.write("path".getBytes());
                    lg=formatIdxPoint(buf,idxP);
                    fW.write(buf,0,lg);
                    fW.write(sXCPoint);
                    fW.write(sXCName);
                    // Write line coordinates
                    fW.write(sXSLine);
                    int idxLast=1-idxB;
                    fW.write(bLon[idxLast],0,iLon[idxLast]);
                    fW.write(',');
                    fW.write(bLat[idxLast],0,iLat[idxLast]);
                    fW.write(',');
                    fW.write(bVal[idxLast],0,iVal[idxLast]);
                    fW.write(',');
                    fW.write(bLon[idxB],0,iLon[idxB]);
                    fW.write(',');
                    fW.write(bLat[idxB],0,iLat[idxB]);
                    fW.write(',');
                    fW.write(bVal[idxB],0,iVal[idxB]);
                    fW.write(sXCLine);
                    fW.write(sXSStyle);
                    fW.write(clTransparent);
                    // Style is Blue Green Red
                    fW.write(clMapBlue[idxC]);
                    fW.write(clMapGreen[idxC]);
                    fW.write(clMapRed[idxC]);
                    fW.write(sXCStyle);
                    fW.write(sXCPlace);
                    
                    idxB=1-idxB;
                }
                fW.write(sXCFolder);
            }
            
            fW.write(sXCFolder);
            
            fW.write(sXTOpen);
            fW.write(sXTVisibility);
            fW.write(sXCDoc);
            fW.write(sXCKMLHead);
            fW.flush();
            fW.close();
        } catch (SecurityException ex) {
            ex.printStackTrace();
        } catch (IOException ex) {
            ex.printStackTrace();
        }
        
    }
    
    public void scanMinMaxLegendValues(int dataFlag) {
        // look for min and max values
        myMinVal=Double.POSITIVE_INFINITY;
        myMaxVal=Double.NEGATIVE_INFINITY;
        
        int nbTrk=1;
        if(myVect!=null) nbTrk=myVect.size();
        
        TrackRecord trCur=myTr;
        
        for(int idxTrk=0; idxTrk<nbTrk; idxTrk++) {
            if(myVect!=null) trCur=(TrackRecord) myVect.elementAt(idxTrk);
            
            for(int idxP=trCur.vPoints.size()-1; idxP>=0; idxP--) {
                TrackPoint tPt= (TrackPoint) trCur.vPoints.elementAt(idxP);
                double v=0;
                switch(dataFlag) {
                    case DF_ALTITUDE: v=tPt.altSeaLevel; break;
                    case DF_TIME: v=tPt.durationHere; break;
                    case DF_SPEED: v=tPt.speed; break;
                }
                if(v>myMaxVal) myMaxVal=v;
                if(v<myMinVal) myMinVal=v;
            }
        }
        // Avoid /0 error 
        if(myMaxVal==myMinVal) myMaxVal=myMinVal+1;
    }
    
    public int colorIndexOf(double val) {
        return (int)
        ((val-myMinVal)*(clMapRed.length-1)/(myMaxVal-myMinVal)+0.5d);
    }
    
    public int formatAltitude(byte[] buf, double val) {
        return Converters.writeDblInBuffer(buf,0,6,1,val);
    }
    
    public int formatDuration(byte[] buf, double val) {
        return Converters.writeIntInBuffer(buf,0,5, (int)val);
    }
    
    public int formatSpeed(byte[] buf, double val) {
        return Converters.writeDblInBuffer(buf,0,5,1,val);
    }
    
    public int formatLatOrLon(byte[] buf, double val) {
        return Converters.writeDblInBuffer(buf,0,10,6,val);
    }
    
    public int formatIdxPoint(byte[] buf, int idx) {
        return Converters.writeIntInBuffer(buf,0,4,idx);
    }
    
}